package vampirism.core.server;

import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
import com.esotericsoftware.minlog.Log;

import vampirism.core.shared.*;
import vampirism.core.shared.Network.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

public class ServerSide {

	Random rand = new Random(Network.seed);
	Server server;
	
	// Map consists of 256 * 256 tiles
	public Tile[][] map;

	// Contains all registered accounts
	// Each account has a list of players - each player may or may not be online
	public List<Account> accounts = new ArrayList<Account>();
	
	// Contains all ONLINE players (doesn't contain offline/dead players)
	public List<Player> players = new ArrayList<Player>();
	
	// Contains all alive bullets that are currently in game
	public List<Bullet> bullets = new ArrayList<Bullet>();

	// A list of connections for all online players?
	public List<AccountConnection> connections = new ArrayList<AccountConnection>();
	
	//List of all tiles seperated into biomes
	public List<Biome> biomes = new ArrayList<Biome>();

	
	public Updater serverUpdater;

	public static void main(String[] args) {
		Log.set(Log.LEVEL_DEBUG);
		ServerSide serverSide = new ServerSide();
	}
	
	public void simulateUpdatePlayer(AccountConnection con) {
		simulateUpdatePlayer(con.account.alive.index, 
					   con.account.alive.moving,
					   con.account.alive.pos.x,
					   con.account.alive.pos.y);
	}
	
	public void simulateUpdatePlayer(short playerIndex, boolean[] moving, float syncX, float syncY){
		// Send update to all TCP connections
		UpdatePlayer packet = new UpdatePlayer();
		packet.index = playerIndex;
		packet.moving = moving;
		packet.syncX = syncX;
		packet.syncY = syncY;
		server.sendToAllTCP(packet);
	}
	
	//Send hit packet to all clients after projectile collision
	public void simulateHit(short sourceId,short targetId,short damage){
		Hit packet = new Hit();
		packet.sourceId = sourceId;
		packet.targetId = targetId;
		packet.damage = damage;
		server.sendToAllTCP(packet);
	}
	
	public void simulateGainExp(short bodyId, short amount){
		GainExp packet = new GainExp();
		packet.bodyIndex = bodyId;
		packet.amount = amount;
		server.sendToAllTCP(packet);
	}
	
	public void simulateRemoveBody(short index) {
		// Send remove player to all TCP connections
		RemoveBody packet = new RemoveBody();
		packet.bodyIndex = index;
		server.sendToAllTCP(packet);
	}
	
	public void simulateAddBullet(int exceptConId, Bullet newBullet){
		AddBullet packet = new AddBullet();
		packet.bullet = newBullet;
		server.sendToAllExceptTCP(exceptConId, packet);
	}

	public void simulateAddBullet(Bullet newBullet){
		AddBullet packet = new AddBullet();
		packet.bullet = newBullet;
		server.sendToAllTCP(packet);
	}
	
	public void simulateLaunchGame(short alphaId){
		LaunchGame packet = new LaunchGame();
		packet.alphaId = alphaId;
		server.sendToAllTCP(packet);
	}
	
	public ServerSide() {

		//Initialize server with max TCP and UDP packet buffer sizes
		server = new Server(65536,65536) {
			
			protected Connection newConnection() {
				// By providing our own connection implementation, we can store per
				// connection state without a connection ID to state look up.
				AccountConnection con = new AccountConnection();
				
				System.out.println("#SERVER: new connection established! "+con.getID());
				
				connections.add(con);
				
				return con;
			}
		};

		// For consistency, the classes to be sent over the network are
		// registered by the same method for both the client and server.
		Network.register(server);
		
		System.out.println("#SERVER: server initialized!");

		server.addListener(new Listener() {
			public void received(Connection c, Object object) {
				// We know all connections for this server are actually
				// AccountConnections.
				AccountConnection connection = (AccountConnection) c;
				
				//System.out.println("#SERVER: received packet: "+object.toString());

				if (object instanceof Login) {

					Login login = (Login) object;
					LoginResult loginResult = new LoginResult();

					if (!Validator.isValidEmail(login.email)) {
						loginResult.result = LoginResultType.INVALID_EMAIL;
						c.sendTCP(loginResult);
						return;
					}
					if (!Validator.isValidPass(login.pass)) {

						loginResult.result = LoginResultType.INVALID_PASS;
						c.sendTCP(loginResult);
						return;
					}

					// If got to this point - validation was successful!

					for (int i = 0; i < accounts.size(); i++)
						if (accounts.get(i).email.toLowerCase().equals(login.email.toLowerCase())) {// Email
																									// already
																									// exists

							if (accounts.get(i).password.equals(login.pass)) {
								// Correct password - logging in
								connection.account = accounts.get(i);
								loginResult.result = LoginResultType.LOGIN_SUCCESS;
								loginResult.myPlayers = connection.account.players;
								c.sendTCP(loginResult);
							} else {
								// Incorrect password - disconnecting
								loginResult.result = LoginResultType.INCORRECT_PASS;
								c.sendTCP(loginResult);
							}
							return;
						}

					// Email doesn't exist in any of existing accounts - must
					// create new!

					connection.account = new Account();
					connection.account.id = accounts.size();
					connection.account.email = login.email.toLowerCase(); // Prevents different accounts with same capitalized email
					connection.account.password = login.pass;
					accounts.add(connection.account);

					loginResult.result = LoginResultType.REG_SUCCESS;
					c.sendTCP(loginResult);
					return;
				} else if (object instanceof CreatePlayer) {

					// Can only get to this point if already logged in or
					// registered.

					CreatePlayer createPlayer = (CreatePlayer) object;
					CreatePlayerResult createPlayerResult = new CreatePlayerResult();

					if (!Validator.isValidPlayerName(createPlayer.name)) {
						createPlayerResult.result = CreatePlayerResultType.INVALID_NAME;
						c.sendTCP(createPlayerResult);
						return;
					}
					if (!Validator.isValidPlayerType(createPlayer.type)) {
						createPlayerResult.result = CreatePlayerResultType.INVALID_TYPE;
						c.sendTCP(createPlayerResult);
						return;
					}
					if (!Validator.isValidPlayerAmount(connection.account.players.size())) {
						createPlayerResult.result = CreatePlayerResultType.TOO_MANY_CHARACTERS;
						c.sendTCP(createPlayerResult);
						return;
					}

					for (int i = 0; i < accounts.size(); i++) {
						for (int j = 0; j < accounts.get(i).players.size(); j++) {
							if (accounts.get(i).players.get(j).name.toLowerCase().equals(createPlayer.name.toLowerCase())) {
								createPlayerResult.result = CreatePlayerResultType.NAME_ALREADY_EXISTS;
								c.sendTCP(createPlayerResult);
								return;
							}
						}
					}

					// All validation checks passed - creating the new player
					Player newPlayer = Creator.createPlayer(connection.account.id, connection.account.players.size(), createPlayer.name, createPlayer.type);

					newPlayer.tileIndex = serverUpdater.getLocationIndex(newPlayer.pos.x, newPlayer.pos.y);
					
					connection.account.players.add(newPlayer);

					createPlayerResult.result = CreatePlayerResultType.SUCCESS;
					createPlayerResult.myPlayers = connection.account.players;
					c.sendTCP(createPlayerResult);
					return;

				} else if (object instanceof SelectPlayer) {
					SelectPlayer selectPlayer = (SelectPlayer) object;
					SelectPlayerResult selectPlayerResult = new SelectPlayerResult();

					for (int i = 0; i < connection.account.players.size(); i++) {
						if (connection.account.players.get(i).accountPlayerNum == selectPlayer.accountPlayerNum) {

							// Player selected this player and pressed 'play'
							connection.account.alive = connection.account.players.get(i);

							// Set the index of this player (because it just came online)
							connection.account.alive.index = (short) players.size();

							players.add(connection.account.alive);
							
							if(players.size() == 1){
								TimerTask launchTask = new TimerTask() {
									
									@Override
									public void run() {
										short alphaId = (short) rand.nextInt(players.size());
										simulateLaunchGame(alphaId);
										serverUpdater.launchGame(alphaId);
									}
								};
								Timer launchTimer = new Timer();
								launchTimer.schedule(launchTask, Network.startWaitingPeriod);
							}

							selectPlayerResult.result = SelectPlayerResultType.SUCCESS;
							selectPlayerResult.allPlayers = players;
							selectPlayerResult.gameLaunched = serverUpdater.gameLaunched;
							selectPlayerResult.myPlayerIndex = connection.account.alive.index;
							c.sendTCP(selectPlayerResult);
							
							// Send add player to other TCP connections
							AddPlayer addPlayer = new AddPlayer();
							addPlayer.newPlayer = connection.account.alive;
							server.sendToAllExceptTCP(connection.getID(), addPlayer);

							return;
						}
					}

					// If got to this point - no such player was found in the account
					selectPlayerResult.result = SelectPlayerResultType.INVALID_ID;
					c.sendTCP(selectPlayerResult);
					return;
				} else if (object instanceof Move) {
					Move move = (Move) object;

					if (connection.account.alive != null) {

						// Save the last input of the client (WASD keys)
						connection.account.alive.moving = move.moving;
						
						connection.account.alive.pos.x = move.syncX;
						connection.account.alive.pos.y = move.syncY;

						simulateUpdatePlayer(connection);
					}
				} else if (object instanceof Shoot) {
					
					Shoot shoot = (Shoot) object;
					
					Bullet newBullet = Creator.createBullet(connection.account.alive, shoot.rotation);
					bullets.add(newBullet);
					
					simulateAddBullet(connection.getID(),newBullet);
				}
			}

			public void disconnected(Connection c) {
				AccountConnection connection = (AccountConnection) c;
				if (connection.account != null) {
					if (connection.account.alive != null) {
						serverUpdater.removeBody(connection.account.alive.index);
					}
					c.close();
				}
			}
		});

		try {
			server.bind(Network.port);
		} catch (IOException e) {
			e.printStackTrace();
		}
		server.start();
		
		map = Tile.createMap();
		
		biomes = Creator.createBiomes(map);
		
		serverUpdater = new Updater(map,players,bullets,biomes);
		serverUpdater.isClient = false;//Updater is used by both - server and client
		serverUpdater.serverSide = this;//Give it reference to this class (to be able to send packets)

		Thread updateThread = new Thread(serverUpdater);
		updateThread.start();
	}

	public static class AccountConnection extends Connection {
		public Account account;
	}

}